﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace pdfer.Filters
{
    public class LZWDecodeFilter : IFilter
    {
        // 压缩好的数据由一系列9-12bit长度的数据组成
        // 0-255 代表数据
        // 256 cleartable
        // 257 EOD
        // 最初的LZW 表仅仅有9位 258个固定的编码，随着编码的进行，越来越多的编码附加到这个表中
        // 目前编码的长度没有超多12位的所以最大的编码是4095

        private Byte[][] stringTable;
        private Int32 stringTableIdx;
        /// <summary>
        /// 9 10 11 12
        /// </summary>
        private Int32 usedBit;
        private Stream stream;
        private Int32 nextData;
        private Int32 currentByte;
        private Int32 _nextBits;
        internal int[] AndTable = [511, 1023, 2047, 4095];

        private void InitStringTable()
        {
            stringTable = new Byte[4096][];
            for (var i = 0; i < 256; i++)
            {
                stringTable[i] = [(Byte)i];
            }
            stringTableIdx = 258;
            usedBit = 9;
        }

        private Int32 Next(ReadOnlySpan<Byte> compressedData) 
        {
            try
            {
                nextData = (nextData << 8) | (compressedData[currentByte++] & 0xFF);
                _nextBits += 8;
                if (_nextBits < usedBit)
                {
                    nextData = (nextData << 8) | (compressedData[currentByte++] & 0xFF);
                    _nextBits += 8;
                }
                int result = (nextData >> _nextBits - usedBit) & AndTable[usedBit - 9];
                _nextBits -= usedBit;
                return result;
            }
            catch 
            {
                return 257;
            }
        }

        /// <summary>
        ///  <inheritdoc/>
        ///  解码 线程不安全的
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public byte[] Decode(ReadOnlySpan<Byte> input)
        {
            using var _stream = new MemoryStream();
            stream = _stream;
            InitStringTable();
            nextData = 0;
            _nextBits = 0;
            currentByte = 0;
            Int32 nextCode = 0;
            Int32 num = 0;
            while (true)
            {
                // 获取下一个
                nextCode = Next(input);
                if(nextCode == 257)
                {
                    // EOD
                    break;
                }
                else if(nextCode == 256)
                {
                    InitStringTable();
                    nextCode = Next(input);
                    if (nextCode == 257)
                    {
                        // EOD
                        break;
                    }
                    WriteString(stringTable[nextCode].ToArray());
                    num = nextCode;
                }
                else if(nextCode < stringTableIdx)
                {
                    var data = stringTable[nextCode];
                    WriteString(data.ToArray());
                    AddStringToTable(stringTable[num], data[0]);
                }
                else
                {
                    var data = stringTable[num];
                    data = ArrayAppend(data, data[0]);
                    //data.Add(data[0]);
                    WriteString(data.ToArray());
                    AddStringToTable(data);
                    num = nextCode;
                }
            }
            return _stream.ToArray();
        }

        private byte[] ArrayAppend(byte[] data, byte v)
        {
            var newArr = new byte[data.Length + 1];
            Array.Copy(data, newArr, data.Length);
            newArr[data.Length] = v;
            return newArr;
        }

        private void AddStringToTable(Byte[] data, byte v)
        {
            var newArr = new byte[data.Length + 1];
            Array.Copy(data, newArr, data.Length);
            newArr[data.Length] = v;
            stringTable[stringTableIdx++] = newArr;
            switch (stringTableIdx)
            {
                case 511:
                    usedBit = 10;
                    break;
                case 1023:
                    usedBit = 11;
                    break;
                case 4095:
                    usedBit = 12;
                    break;
            }
        }

        private void AddStringToTable(Byte[] str)
        {
            stringTable[stringTableIdx++] = str;
            switch (stringTableIdx)
            {
                case 511:
                    usedBit = 10;
                    break;
                case 1023:
                    usedBit = 11;
                    break;
                case 4095:
                    usedBit = 12;
                    break;
            }
        }

        private void WriteString(byte[] str)
        {
            stream.Write(str, 0, str.Length);
        }

        private byte[] ComposeString(byte[] oldstring, byte newstring)
        {
            int num = oldstring.Length;
            byte[] array = new byte[num + 1];
            Array.Copy(oldstring, 0, array, 0, num);
            array[num] = newstring;
            return array;
        }

        /// <summary>
        /// <inheritdoc/>
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public byte[] Encode(ReadOnlySpan<Byte> input)
        {
            throw new NotImplementedException();
        }
    }
}
